package com.quectel.leshan.quecleshan;

import lombok.SneakyThrows;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.MessageObserverAdapter;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.network.Endpoint;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.leshan.core.californium.EndpointContextUtil;
import org.eclipse.leshan.core.model.ObjectLoader;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.*;
import org.eclipse.leshan.core.response.*;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.impl.LeshanServer;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.model.StaticModelProvider;
import org.eclipse.leshan.server.observation.ObservationListener;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationListener;
import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.List;
import java.util.Scanner;

public class LWM2MServer {

    private static final String[] ddfpaths = new String[]{"LWM2M_Security-v1_0.xml", "LWM2M_Security-v1_1_1.xml",
            "LWM2M_Server-v1_0.xml", "LWM2M_Server-v1_1_1.xml", "LWM2M_Access_Control-v1_0_3.xml",
            "LWM2M_Device-v1_0_3.xml", "LWM2M_Device-v1_1.xml",
            "LWM2M_Connectivity_Monitoring-v1_0_2.xml", "LWM2M_Connectivity_Monitoring-v1_1.xml",
            "LWM2M_Connectivity_Monitoring-v1_2.xml", "LWM2M_Firmware_Update-v1_0_3.xml",
            "LWM2M_Location-v1_0_2.xml", "LWM2M_Connectivity_Statistics-v1_0_4.xml",
            "LWM2M_OSCORE-v1_0_1.xml", "19.xml"};

    private static final Logger logger = LoggerFactory.getLogger(LWM2MServer.class);

    public static void main(String[] args) throws InterruptedException {
        LeshanServerBuilder builder = new LeshanServerBuilder();

        //models
        List<ObjectModel> models = ObjectLoader.loadDdfResources("/models/", ddfpaths);
        LwM2mModelProvider modelProvider = new StaticModelProvider(models);
        builder.setObjectModelProvider(modelProvider);

        LeshanServer server = builder.build();

        NetworkConfig coapConfig = LeshanServerBuilder.createDefaultNetworkConfig();
        coapConfig.setString(NetworkConfig.Keys.DTLS_RESPONSE_MATCHING, "RELAXED");
        builder.setCoapConfig(coapConfig);

        //设备注册时的Listener
        server.getRegistrationService().addListener(new RegistrationListener() {

            @SneakyThrows
            public void registered(Registration registration, Registration previousReg,
                                   Collection<Observation> previousObsersations) {
                System.out.println("new device: " + registration.getEndpoint());

                try {
                    //向Client发请求获取相应的数据（比如查询版本号、电池等）
                    ReadResponse response = server.send(registration, new ReadRequest(3, 0, 13));
                    if (response.isSuccess()) {
                        System.out.println("Device time:" + ((LwM2mResource) response.getContent()).getValue());
                    } else {
                        System.out.println("Failed to read:" + response.getCode() + " " + response.getErrorMessage());
                    }

                    response = server.send(registration, new ReadRequest(3, 0, 3));
                    System.out.println("Firmware version is " + ((LwM2mResource) response.getContent()).getValue());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //向客户端发起observe资源的请求，客户端资源变化时会触发 ObservationListener的 onResponse （需添加ObservationListener监听器到server中）
                ObserveRequest request = new ObserveRequest("/3/0/9"); //跟普通请求一样，Client会调用设备的 read/write 等方法获取或处理响应的值（read/write等方法继承自BaseInstanceEnabler）
                server.send(registration, request, new ResponseCallback() {
                    @Override
                    public void onResponse(LwM2mResponse response) {  //这里只会触发一次，即只在第一次Client返回响应值的时候触发（不影响ObservationListener的onResponse触发）
                        LwM2mNode content = ((ReadResponse) response).getContent();
                        System.out.println("Observe Response --- > " + content);
                    }
                }, new ErrorCallback() {
                    @Override
                    public void onError(Exception e) {
                        System.out.println("Observe Response error");
                        e.printStackTrace();
                    }
                });
            }

            public void updated(RegistrationUpdate update, Registration updatedReg, Registration previousReg) {
                System.out.println("device is still here: " + updatedReg.getEndpoint());
            }

            public void unregistered(Registration registration, Collection<Observation> observations, boolean expired,
                                     Registration newReg) {
                System.out.println("device left: " + registration.getEndpoint());
            }
        });

        //Observation Listener（Client response时触发 onResponse）
        server.getObservationService().addListener(new ObservationListener() {
            @Override
            public void newObservation(Observation observation, Registration registration) {
                System.out.println(">>>>>>>>>>>>>>>>>>>>> newObservation");
            }

            @Override
            public void cancelled(Observation observation) {
                System.out.println(">>>>>>>>>>>>>>>>>>>>> cancelled");
            }

            @Override
            public void onResponse(Observation observation, Registration registration, ObserveResponse observeResponse) {
                System.out.println(">>>>>>>>>>>>>>>>>>>>> onResponse");
                System.out.println("OnResponse -- " + observeResponse.getContent());
                LwM2mResource content = (LwM2mResource)observeResponse.getContent();
                if (ResourceModel.Type.OPAQUE.equals(content.getType())) {
                    System.out.println("Observe " + observation.getPath().toString() + " Response Content is : " + new String((byte[]) content.getValue()));
                }
            }

            @Override
            public void onError(Observation observation, Registration registration, Exception e) {
                System.out.println(">>>>>>>>>>>>>>>>>>>>> onError");
            }
        });

        //Client请求服务端资源，服务端返回相应的内容
        server.getCoapServer().add(new HelloCoapResource("request_test"));

        server.start();

        inputCmd(server);
    }

    /*
    向coap客户端发送请求，请求客户端数据或提交数据到客户端

    1、请求发送可以参考leshan的：CaliforniumLwM2mRequestSender.send(Registration, DownlinkRequest<T>, long, ResponseCallback<T>, ErrorCallback)
    2、coapRequest的创建可以参考 org.eclipse.leshan.server.californium.impl.CoapRequestBuilder.setTarget
     */
    private static void sendRequestToClient(LeshanServer server) {
        Request coapRequest = Request.newPost();
        coapRequest.addMessageObserver(new MessageObserverAdapter() {
            @Override
            public void onResponse(Response response) {
                System.out.println("Get Response from client : " + response.getCode() + " -- " + response.getPayloadString());
            }
        });

        Registration destination = server.getRegistrationService().getByEndpoint("test_device_123");
        EndpointContext context = EndpointContextUtil.extractContext(destination.getIdentity());
        coapRequest.setDestinationContext(context);
        coapRequest.getOptions().setAccept(MediaTypeRegistry.TEXT_PLAIN);
        coapRequest.getOptions().addUriPath("hello_client");
        coapRequest.setPayload("hello abc123");

        Endpoint endpoint = server.getCoapServer().getEndpoint(destination.getRegistrationEndpointAddress());
        endpoint.sendRequest(coapRequest);
    }

    private static void inputCmd(LeshanServer server) {
        try (Scanner scanner = new Scanner(System.in)){
            while (scanner.hasNext()) {
                String command = scanner.next();
                switch (command) {
                    case "rc":
                        System.out.println("Send request to client ...");
                        try {
                            sendRequestToClient(server);
                        } catch (Exception e) {
                            e.printStackTrace();
                            scanner.next();
                        }
                        break;
                    case "wr":
                        Registration destination1 = server.getRegistrationService().getByEndpoint("test_device_123");
                        //WriteRequest writeRequest = new WriteRequest(ContentFormat.JSON,19,0,0,"write test abc123".getBytes());
                        LwM2mNode resource = LwM2mSingleResource.newBinaryResource(0, "write test abc123".getBytes());
                        WriteRequest writeRequest = new WriteRequest(WriteRequest.Mode.REPLACE,ContentFormat.JSON,"19/0/0",resource);
                        try {
                            server.send(destination1, writeRequest);
                        } catch (InterruptedException e) {
                            scanner.next();
                        }
                        break;
                    case "exec":
                        Registration destination = server.getRegistrationService().getByEndpoint("test_device_123");
                        ExecuteRequest executeRequest = new ExecuteRequest("19/0/6", "abc123efg");
                        try {
                            server.send(destination, executeRequest);
                        } catch (InterruptedException e) {
                            scanner.next();
                        }
                        break;
                    case "obv":
                        Registration destination2 = server.getRegistrationService().getByEndpoint("test_device_123");
                        ObserveRequest request = new ObserveRequest("/19/0/0");
                        try {
                            server.send(destination2, request);
                        } catch (InterruptedException e) {
                            scanner.next();
                        }
                        break;
                    default:
                        System.out.println("Unknown command !");
                }
            }
        }
    }

}
